#include <arch/pic.h>
#include <arch/interrupt.h>
#include <os/hardirq.h>
#include <lib/type.h>
#include <arch/io.h>
#include <lib/type.h>

// set chips work mode
static int PicSetWorkMode(uint8_t chips, uint8_t workmode)
{
    uint8_t icw1 = workmode;
    if (chips >= 2)
        return -1;

    PitSendCMD(chips, icw1);
    return 0;
}

// set chips start vector offfset
static int PicVectorSet(uint8_t chips, uint8_t vector)
{
    // icw2 bits3-bits7 are interrupt vector offset,bits 0-2 reserved to irq number
    uint8_t icw2 = vector & PIC_ICW2_VEC;
    if (chips >= 2)
        return -1;

    // send to chips data port
    PicSendData(chips, icw2);
    return 0;
}

static int PicSetOpmode(uint8_t chips, uint8_t opmode)
{
    uint8_t icw4 = opmode;

    if (chips >= 2)
        return -1;

    PicSendData(chips, icw4);
    return 0;
}

// set a irq used to make connect in between two chips
static int PicSetIrqCascaded(uint8_t chips, uint8_t irq)
{
    // if is master chips,icw3 is specific irq used to connect to slave chips
    // if is slave chips,icw3 is irq number which master chip connect to slave chips,and only have bits0-bits2 all bits can be used
    uint8_t icw3 = (chips == 0 ? (1 << irq) : (irq));

    if (chips >= 2)
        return -1;

    // send icw3 to assign which irq line used to another chips
    PicSendData(chips, icw3);
    return 0;
}

void PicInit()
{
    uint8_t mask1, mask2;
    uint8_t workmode = PIC_ICW1_INIT | PIC_ICW1_ICW4; // used to set chips work mode and indiate if need ICW4
    uint8_t off1 = 0x20, off2 = 0x28;
    uint8_t mirq = 2, sirq = 2;                               // set irq line which used to cascaded
    uint8_t mopmode = PIC_ICW4_8086, sopmode = PIC_ICW4_8086; // set opmode is 8086

    // save mask
    mask1 = In8((uint16_t)PIC_MASTER_DATA);
    mask2 = In8((uint16_t)PIC_SLAVE_DATA);

    // send icw1 to every PIC chips
    PicSetWorkMode(PIC_MASTER, workmode);
    PicSetWorkMode(PIC_SLAVE, workmode);
    // send icw2 to set chips vector offset of IVT
    // remap irq0-7,irq8-15 to assign intterupt vector
    PicVectorSet(PIC_MASTER, off1); // master chips irq0-7 use vector 0x20 as start base
    PicVectorSet(PIC_SLAVE, off2);  // slave chips ir8-15 use vector 0x28 as start base
    // send icw3 to set irq line used to connect chips communcation each other
    PicSetIrqCascaded(PIC_MASTER, mirq);
    PicSetIrqCascaded(PIC_SLAVE, sirq);
    // send icw4 to set chips operator mode,there  we only set chips use 8086 mode
    PicSetOpmode(PIC_MASTER, mopmode);
    PicSetOpmode(PIC_SLAVE, sopmode);

    // restore mask
    Out8((uint16_t)PIC_MASTER_DATA, mask1);
    Out8((uint16_t)PIC_SLAVE_DATA, mask2);

    // temp disable all irq,because we will install handler for irq
    Out8((uint16_t)PIC_MASTER_DATA, (uint8_t)0xff);
    Out8((uint16_t)PIC_SLAVE_DATA, (uint8_t)0xff);

    KPrint("[pic] pic init done.\n");
}

// send EOI cmd to end targe irq
void PicSendEOI(uint8_t irq)
{
    if(irq<8)
    {
        Out8(PIC_MASTER_CMD, PIC_OCW2_EOF);
    }
    else
    {   
        Out8(PIC_MASTER_CMD, PIC_OCW2_EOF);
        Out8(PIC_SLAVE_CMD, PIC_OCW2_EOF);
    }
}

int PitSendCMD(uint8_t chips, uint8_t cmd)
{
    if (chips >= 2)
        return 1;

    // master PIC chips
    if (!chips)
    {
        Out8((uint16_t)PIC_MASTER_CMD, cmd);
    }
    else
    {
        // slave PIC chips
        Out8((uint16_t)PIC_SLAVE_CMD, cmd);
    }
    return 0;
}

int PicReadCMD(uint8_t chips, uint8_t *cmd)
{
    if (chips >= 2)
        return 1;
    // master PIC chips
    if (!chips)
    {
        *cmd = In8(PIC_MASTER_CMD);
    }
    else
    {
        // slave PIC chips
        *cmd = In8(PIC_SLAVE_CMD);
    }
    return 0;
}

int PicSendData(uint8_t chips, uint8_t data)
{
    if (chips >= 2)
        return 1;

    // master PIC chips
    if (!chips)
    {
        Out8(PIC_MASTER_DATA, data);
    }
    else
    {
        // slave PIC chips
        Out8(PIC_SLAVE_DATA, data);
    }
    return 0;
}

int PicReadData(uint8_t chips, uint8_t *data)
{
    if (chips >= 2)
        return 1;

    // master PIC chips
    if (!chips)
    {
        *data = In8(PIC_MASTER_DATA);
    }
    else
    {
        // slave PIC chips
        *data = In8(PIC_SLAVE_DATA);
    }
    return 0;
}

// mask targe IRQ
void PicSetINTMask(uint8_t irq)
{
    uint8_t mask1, mask2;

    // if irq<8 just is master chips,irq is 0-7
    if (irq < 8)
    {
        // read mask register
        PicReadData(PIC_MASTER, &mask1);
        PicSendData(PIC_MASTER, mask1 | (1 << irq));
    }
    else
    {
        // if is slave chips,we must use irq-8 to make irq between 0 of 7
        // read mask register
        PicReadData(PIC_SLAVE, &mask2);
        PicSendData(PIC_SLAVE, mask2 | (1 << (irq - 8)));
    }
    // KPrint("[pic] disable interrupt irq %u\n", irq);
}

// cancel targe IRQ mask
void PicClearINTMask(uint8_t irq)
{
    uint8_t mask1, mask2;

    if (irq < 8)
    {
        PicReadData(PIC_MASTER, &mask1);
        PicSendData(PIC_MASTER, mask1 & ~(1 << irq));
    }
    else
    {
        // enable irq2 where connected to slave chips
        PicReadData(PIC_MASTER, &mask1);
        PicSendData(PIC_MASTER, mask1 & ~(1 << IRQ2_SLAVE));
        PicReadData(PIC_SLAVE, &mask2);
        PicSendData(PIC_SLAVE, mask2 & ~(1 << (irq - 8)));
    }
}

// disable PIC chips
void PicDisable()
{
    // disable master pic
    PicSendData(PIC_MASTER, 0xff);
    // disable slave pic
    PicSendData(PIC_SLAVE, 0xff);
}

uint16_t PicReadIRR()
{
    uint8_t ir1 = 0, ir2 = 0;
    uint16_t ir = 0;

    // send PIC_READ_IRR cmd to evey command register
    PitSendCMD(PIC_MASTER, PIC_OCW3_RIRR);
    // read IRR status from every cmd register
    PicReadData(PIC_MASTER, &ir1);
    PitSendCMD(PIC_SLAVE, PIC_OCW3_RIRR);
    PicReadData(PIC_SLAVE, &ir2);
    // use master ir status and slave ir status make all ir status,thear are a chain
    // master irq is 0-7,slave irq is 8-15
    ir = ((uint16_t)ir2 << 8) | (uint16_t)ir1;
    KPrint("[pic] read irr %x\n", ir);

    return ir;
}

uint16_t PicReadISR()
{
    uint8_t is1 = 0, is2 = 0;
    uint16_t is = 0;

    // send PIC_READ_ISR cmd to command register
    PitSendCMD(PIC_MASTER, PIC_OCW3_RISR);
    PitSendCMD(PIC_SLAVE, PIC_OCW3_RISR);
    // read ISR status from cmd register
    PicReadData(PIC_MASTER, &is1);
    PicReadData(PIC_SLAVE, &is2);
    is = (uint16_t)is2 << 8 | (uint16_t)is1;
    KPrint("[pic] read isr %x\n", is);
    return is;
}

// install interrrupt for irq
static void PicInstall(uint8_t irq, irq_handler_t fun)
{
    IrqRegisterInterrupt(irq, (interrupt_handler_t)fun);
    // KPrint("[pic] irq %d register func!\n", irq);
}

// uninstall interrupt for irq
static void PicUnInstall(uint8_t irq)
{
    IrqUnRegisterInterrupt(irq);
    // KPrint("[pic] irq %d unregister func\n", irq);
}

int InterruptDoIrq(trap_frame_t *frame)
{
    uint64_t irq;

    irq = frame->vec_num - 0x20;

    if (!IrqHandle(irq, frame))
    {
        return -1;
    }
    return 0;
}

// system interrupt controller
hardware_interrupt_controller_t interrupt_controller = {
    .install = PicInstall,
    .uninstall = PicUnInstall,
    .init = PicInit,
    .enable = PicClearINTMask,
    .disable = PicSetINTMask,
    .ack = PicSendEOI,
};